/*-----------------------------------------------------------------------------
 * File: sr_main.c
 * Date: Spring 2002
 * Authors: Guido Apanzeller, Vikram Vijayaraghaven, Martin Casado
 * Contact: casado@stanford.edu
 *
 * Based on many generations of sr clients including the original c client
 * and bert.
 *
 * Description:
 *
 * Driver file for sr
 *
 *---------------------------------------------------------------------------*/

#ifdef _SOLARIS_
#define __EXTENSIONS__
#endif /* _SOLARIS_ */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>

#ifdef _LINUX_
#include <getopt.h>
#endif /* _LINUX_ */

#include "sr_dumper.h"
#include "sr_router.h"
#include "sr_rt.h"

extern char* optarg;

/*-----------------------------------------------------------------------------
 *---------------------------------------------------------------------------*/

#define DEFAULT_PORT 12345
#define DEFAULT_HOST "vrhost"
#define DEFAULT_SERVER "171.67.71.18"
#define DEFAULT_RTABLE "rtable"
#define DEFAULT_TOPO 0

static void usage(char* );
void test_arp_functionality(struct sr_instance* sr);
void test_route_packet(struct sr_instance *);
static void sr_init_instance(struct sr_instance* );
static void sr_destroy_instance(struct sr_instance* );
static void sr_set_user(struct sr_instance* );

/*-----------------------------------------------------------------------------
 *---------------------------------------------------------------------------*/

int main(int argc, char **argv)
{
    int c;
    char *host   = DEFAULT_HOST;
    char *client = 0;
    char *server = DEFAULT_SERVER;
    char *rtable = DEFAULT_RTABLE;
    unsigned int port = DEFAULT_PORT;
    unsigned int topo = DEFAULT_TOPO;
    char *logfile = 0;
    struct sr_instance sr;

    while ((c = getopt(argc, argv, "hs:v:p:c:t:r:l:")) != EOF)
    {
        switch (c)
        {
            case 'h':
                usage(argv[0]);
                exit(0);
                break;
            case 'p':
                port = atoi((char *) optarg);
                break;
            case 't':
                topo = atoi((char *) optarg);
                break;
            case 'v':
                host = optarg;
                break;
            case 'c':
                client = optarg;
                break;
            case 's':
                server = optarg;
                break;
            case 'l':
                logfile = optarg;
                break;
            case 'r':
                rtable = optarg;
                break;
        } /* switch */
    } /* -- while -- */

    /* -- zero out sr instance -- */
    sr_init_instance(&sr);

    /* -- set up routing table from file -- */
    if(sr_load_rt(&sr, rtable) != 0)
    {
        fprintf(stderr,"Error setting up routing table from file %s\n",
                rtable);
        exit(1);
    }


    printf("Loading routing table\n");
    printf("---------------------------------------------\n");
    sr_print_routing_table(&sr);
    printf("---------------------------------------------\n");

    sr.topo_id = topo;
    strncpy(sr.host,host,32);

    if(! client )
    { sr_set_user(&sr); }
    else
    { strncpy(sr.user, client, 32); }

    /* -- set up file pointer for logging of raw packets -- */
    if(logfile != 0)
    {
        sr.logfile = sr_dump_open(logfile,0,PACKET_DUMP_SIZE);
        if(!sr.logfile)
        {
            fprintf(stderr,"Error opening up dump file %s\n",
                    logfile);
            exit(1);
        }
    }

    Debug("Client %s connecting to Server %s:%d\n", sr.user, server, port);
    Debug("Requesting topology %d\n", topo);


    /* connect to server and negotiate session */
    if(sr_connect_to_server(&sr,port,server) == -1)
    {
        return 1;
    }

    /* call router init (for arp subsystem etc.) */
    sr_init(&sr);
    sr_read_from_server(&sr);

    /*
	struct sr_if* if_walker = sr.if_list;
	struct in_addr mask_addr;
	inet_aton("255.255.255.255", &mask_addr);
    struct in_addr hidden_dest_addr;
    hidden_dest_addr.s_addr = if_walker->ip;
    char * temp = inet_ntoa(hidden_dest_addr);
    temp[12] = temp[12]-1;
    inet_aton(temp,&hidden_dest_addr);
    sr_add_rt_entry(&sr,hidden_dest_addr,hidden_dest_addr,mask_addr,"eth1");
    sr_print_routing_table(&sr);
    */
    sr_init_arp_cache(&sr);

    /* -- whizbang main loop ;-) */
    while( sr_read_from_server(&sr) == 1)
    {
    	/*test_arp_functionality(&sr);*/
    	/*test_route_packet(&sr)*/;
    }

    sr_destroy_instance(&sr);

    return 0;
}/* -- main -- */

void test_route_packet(struct sr_instance* sr)
{
	unsigned int len = 46;
	uint8_t *packet = (uint8_t *)malloc(len * sizeof(uint8_t));

	uint8_t eth_hdr[] = {
			 /* dst mac */
			0x00, 0x1D, 0x09, 0xD3, 0xBA, 0x61, /* src mac */
			0x70, 0x00, 0x1a, 0xc1, 0x00, 0x02,
			0x08, 0x00
	};
	memcpy(packet, eth_hdr, 14);

	char *send_ip = "171.67.240.43";
	char *targ_ip = "128.220.224.115";
	struct in_addr src;
	struct in_addr dst;
	inet_aton(send_ip,&src);
	inet_aton(targ_ip,&dst);
	struct ip *ip_hdr = create_ip(IP_HDR_LEN,IP_VERSION,IP_DEFAULT_TOS,00,IP_ICMP_PROTOCOL,src,dst,28);

	memcpy(packet+14, ip_hdr, 20);

	sr_route_packet(sr, packet, len, "eth0");
}

void test_arp_functionality(struct sr_instance* sr)
{
	/* Test arp request packet routing to router*/
	Debug("Started testing sending arp request to router\n");
	unsigned int len = 42;
	uint8_t *packet;
	packet = (uint8_t *)calloc(sizeof(uint8_t),len);
	uint8_t send_hard_addr[] = {0x00,0x14,0x51,0x78,0x33,0x25};
	char *sip = "192.168.1.6";
	uint32_t send_ip = inet_addr(sip);
	uint8_t targ_hard_addr[] = {0xff,0xff,0xff,0xff,0xff,0xff};
	char *tip = "172.24.74.17";
	uint32_t targ_ip = inet_addr(tip);
	char *interface = {"eth0"};
	struct sr_arphdr *arphdr;
	arphdr = create_arp_hdr(ARP_REQUEST, send_hard_addr, send_ip, targ_hard_addr, targ_ip);
	struct sr_ethernet_hdr *etherhdr = create_ethernet_header(targ_hard_addr, send_hard_addr, ARPHDR_ETHER);
	assert(etherhdr);
	set_ethernet_hdr(packet, etherhdr);
	set_arp_hdr(packet,arphdr);
	sr_handlepacket(sr, packet, len, interface);
	Debug("Ended testing sending arp request to router\n");
	sleep(30);
	/* test arp request to app server */
	Debug("Started testing sending arp request to app server\n");
	send_hard_addr[0] = 0x00;
	send_hard_addr[1] = 0x14;
	send_hard_addr[2] = 0x51;
	send_hard_addr[3] = 0x78;
	send_hard_addr[4] = 0x33;
	send_hard_addr[5] = 0x25;
	tip = "171.67.240.44";
	send_ip = inet_addr(sip);
	targ_ip = inet_addr(tip);
	interface = "eth1";
	arphdr = create_arp_hdr(ARP_REQUEST, send_hard_addr, send_ip, targ_hard_addr, targ_ip);
	etherhdr = create_ethernet_header(targ_hard_addr, send_hard_addr, ARPHDR_ETHER);
	assert(etherhdr);
	set_ethernet_hdr(packet, etherhdr);
	set_arp_hdr(packet,arphdr);
	sr_handlepacket(sr, packet, len, interface);
	Debug("Ended testing sending arp request to app server\n");
	sleep(30);
	/* Test arp reply handling */
	Debug("Started testing sending arp reply to router\n");
	targ_hard_addr[0] = 0x70;
	targ_hard_addr[1] = 0x00;
	targ_hard_addr[2] = 0x1a;
	targ_hard_addr[3] = 0xc1;
	targ_hard_addr[4] = 0x00;
	targ_hard_addr[5] = 0x02;
	tip = "172.24.74.17";
	send_hard_addr[0] = 0x00;
	send_hard_addr[1] = 0x14;
	send_hard_addr[2] = 0x51;
	send_hard_addr[3] = 0x78;
	send_hard_addr[4] = 0x33;
	send_hard_addr[5] = 0x26;
	sip = "171.67.240.44";
	send_ip = inet_addr(sip);
	targ_ip = inet_addr(tip);
	interface = "eth1";
	arphdr = create_arp_hdr(ARP_REPLY, send_hard_addr, send_ip, targ_hard_addr, targ_ip);
	etherhdr = create_ethernet_header(targ_hard_addr, send_hard_addr, ARPHDR_ETHER);
	assert(etherhdr);
	set_ethernet_hdr(packet, etherhdr);
	set_arp_hdr(packet,arphdr);
	sr_handlepacket(sr, packet, len, interface);
	Debug("Ended testing sending arp reply to router\n");
	sleep(30);
	/* Test arp reply handling */
	Debug("Started testing sending arp reply to app server\n");
	targ_hard_addr[0] = 0x00;
	targ_hard_addr[1] = 0x14;
	targ_hard_addr[2] = 0x51;
	targ_hard_addr[3] = 0x78;
	targ_hard_addr[4] = 0x33;
	targ_hard_addr[5] = 0x25;
	tip = "192.168.1.6";
	send_hard_addr[0] = 0x00;
	send_hard_addr[1] = 0x14;
	send_hard_addr[2] = 0x51;
	send_hard_addr[3] = 0x78;
	send_hard_addr[4] = 0x33;
	send_hard_addr[5] = 0x26;
	sip = "171.67.240.44";
	send_ip = inet_addr(sip);
	targ_ip = inet_addr(tip);
	interface = "eth1";
	arphdr = create_arp_hdr(ARP_REPLY, send_hard_addr, send_ip, targ_hard_addr, targ_ip);
	etherhdr = create_ethernet_header(targ_hard_addr, send_hard_addr, ARPHDR_ETHER);
	assert(etherhdr);
	set_ethernet_hdr(packet, etherhdr);
	set_arp_hdr(packet,arphdr);
	sr_handlepacket(sr, packet, len, interface);
	Debug("Ended testing sending arp reply  to app server\n");
}

void test_icmp_echorequest_functionality(struct sr_instance* sr)
{
	/* Test icmp packet routing */
	unsigned int len = 46;
	uint8_t *packet;
	packet = (uint8_t *)malloc(len*sizeof(uint8_t));
	uint8_t send_hard_addr[] = {0x00,0x23,0x4E,0x75,0xAE,0xF6};
	char*  send_ip ="1016413136";
	uint8_t targ_hard_addr[] = {0xff,0xff,0xff,0xff,0xff,0xff};
	char* targ_ip = "1716724044";
	struct in_addr src;
	struct in_addr dst;
	char *interface = {"eth0"};
	struct ip *ip_hdr;
	ip_hdr = (struct ip *)malloc(sizeof(struct ip));
	assert(ip_hdr);
	inet_aton(send_ip,&src);
	inet_aton(targ_ip,&dst);
	ip_hdr=create_ip(IP_HDR_LEN,IP_VERSION,IP_DEFAULT_TOS,00,IP_ICMP_PROTOCOL,src,dst,28);
	struct sr_icmphdr *icmp_hdr;
	assert(icmp_hdr);
	icmp_hdr=create_icmp_header(ICMP_TYPE_ECHO_REQUEST,ICMP_CODE_ECHO_REQUES,0,0,0,0);
	struct sr_ethernet_hdr *etherhdr = create_ethernet_header(targ_hard_addr, send_hard_addr, IPHDR_ETHER);
	assert(etherhdr);
	set_ethernet_hdr(packet, etherhdr);
	set_ip_hdr(packet,ip_hdr);
	set_icmp_hdr(packet,icmp_hdr,ip_hdr);
	sr_handlepacket(sr, packet, len, interface);
}

/*-----------------------------------------------------------------------------
 * Method: usage(..)
 * Scope: local
 *---------------------------------------------------------------------------*/

static void usage(char* argv0)
{
    printf("Simple Router Client\n");
    printf("Format: %s [-h] [-v host] [-s server] [-p port] \n",argv0);
    printf("           [-t topo id] [-r routing table] \n");
    printf("           [-l log file] \n");
    printf("   defaults server=%s port=%d host=%s  \n",
            DEFAULT_SERVER, DEFAULT_PORT, DEFAULT_HOST );
} /* -- usage -- */

/*-----------------------------------------------------------------------------
 * Method: sr_set_user(..)
 * Scope: local
 *---------------------------------------------------------------------------*/

void sr_set_user(struct sr_instance* sr)
{
    uid_t uid = getuid();
    struct passwd* pw = 0;

    /* REQUIRES */
    assert(sr);

    if(( pw = getpwuid(uid) ) == 0)
    {
        fprintf (stderr, "Error getting username, using something silly\n");
        strncpy(sr->user, "something_silly", 32);
    }
    else
    {
        strncpy(sr->user, pw->pw_name, 32);
    }

} /* -- sr_set_user -- */

/*-----------------------------------------------------------------------------
 * Method: sr_destroy_instance(..)
 * Scope: Local
 *
 *
 *----------------------------------------------------------------------------*/

static void sr_destroy_instance(struct sr_instance* sr)
{
    /* REQUIRES */
    assert(sr);

    if(sr->logfile)
    {
        sr_dump_close(sr->logfile);
    }

    /*
    fprintf(stderr,"sr_destroy_instance leaking memory\n");
    */
} /* -- sr_destroy_instance -- */

/*-----------------------------------------------------------------------------
 * Method: sr_init_instance(..)
 * Scope: Local
 *
 *
 *----------------------------------------------------------------------------*/

static void sr_init_instance(struct sr_instance* sr)
{
    /* REQUIRES */
    assert(sr);

    sr->sockfd = -1;
    sr->user[0] = 0;
    sr->host[0] = 0;
    sr->topo_id = 0;
    sr->if_list = 0;
    sr->routing_table = 0;
    sr->logfile = 0;
} /* -- sr_init_instance -- */

/*-----------------------------------------------------------------------------
 * Method: sr_verify_routing_table()
 * Scope: Global
 *
 * make sure the routing table is consistent with the interface list by
 * verifying that all interfaces used in the routing table actually exist
 * in the hardware.
 *
 * RETURN VALUES:
 *
 *  0 on success
 *  something other than zero on error
 *
 *---------------------------------------------------------------------------*/

int sr_verify_routing_table(struct sr_instance* sr)
{
    struct sr_rt* rt_walker = 0;
    struct sr_if* if_walker = 0;
    int ret = 0;

    /* -- REQUIRES --*/
    assert(sr);

    if( (sr->if_list == 0) || (sr->routing_table == 0))
    {
        return 999; /* doh! */
    }

    rt_walker = sr->routing_table;

    while(rt_walker)
    {
        /* -- check to see if interface exists -- */
        if_walker = sr->if_list;
        while(if_walker)
        {
            if( strncmp(if_walker->name,rt_walker->interface,sr_IFACE_NAMELEN)
                    == 0)
            { break; }
            if_walker = if_walker->next;
        }
        if(if_walker == 0)
        { ret++; } /* -- interface not found! -- */

        rt_walker = rt_walker->next;
    } /* -- while -- */

    return ret;
} /* -- sr_verify_routing_table -- */

